﻿using System.Collections;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using Blazor.Core;
using Blazor.Services.Device;
using DynamicData;
using DynamicData.Binding;
using ReactiveUI;

namespace Blazor.ViewModels.DeviceList;

public class DeviceListViewModel: ViewModelBase
{
    private readonly IDeviceService _deviceService;

    private readonly SourceCache<DeviceEntry, Guid> _deviceCache = new (e => e.Id);
    public IObservableCollection<DeviceEntry> Devices { get; } = new ObservableCollectionExtended<DeviceEntry>();

    public readonly ReactiveCommand<Unit, DeviceEntry?> New;
    public readonly ReactiveCommand<Guid, DeviceEntry?> Change;
    public readonly ReactiveCommand<Guid, bool> Delete;

    public Interaction<DeviceEntry?, DeviceEntry?> DeviceEntryInteraction { get; } = new();
    public Interaction<Unit, bool> ConfirmDeleteInteraction { get; } = new();

    private readonly ObservableAsPropertyHelper<bool> _isEditing;
    public bool IsEditing => _isEditing.Value;

    public DeviceListViewModel(IDeviceService deviceService)
    {
        _deviceService = deviceService;

        _deviceCache.Connect()
            .Bind(Devices)
            .Subscribe()
            .DisposeWith(Disposables);

        New = ReactiveCommand.CreateFromTask(NewImpl);
        Change = ReactiveCommand.CreateFromTask<Guid, DeviceEntry?>(ChangeImpl);
        Delete = ReactiveCommand.CreateFromTask<Guid, bool>(DeleteImpl);

        New.IsExecuting
            .CombineLatest(
                Change.IsExecuting,
                Delete.IsExecuting,
                (a, b, c) => a || b || c)
            .ToProperty(this, x => x.IsEditing, out _isEditing);
    }

    public async Task InitAsync()
    {
        var all = await _deviceService.GetAll();

        _deviceCache.Edit(updater =>
        {
            foreach (var entry in all.Select(DeviceEntry.From))
            {
                updater.AddOrUpdate(entry);
            }
        });
    }

    private async Task<DeviceEntry?> NewImpl()
    {
        var result = await DeviceEntryInteraction.Handle(null);
        if (result is not {} entry)
        {
            return null;
        }

        await _deviceService.AddOrUpdate(entry.Id, entry.Description);
        _deviceCache.AddOrUpdate(entry);
        return entry;
    }

    private async Task<DeviceEntry?> ChangeImpl(Guid id)
    {
        var deviceOpt = _deviceCache.Lookup(id);
        if (deviceOpt is not { HasValue: true, Value: var device })
        {
            return null;
        }

        var result = await DeviceEntryInteraction.Handle(device);
        if (result is not {} entry)
        {
            return null;
        }

        await _deviceService.Delete(device.Id);
        await _deviceService.AddOrUpdate(entry.Id, entry.Description);
        _deviceCache.Edit(updater =>
        {
            _deviceCache.Remove(device.Id);
            _deviceCache.AddOrUpdate(entry);
        });
        return entry;
    }

    private async Task<bool> DeleteImpl(Guid id)
    {
        var confirmed = await ConfirmDeleteInteraction.Handle(Unit.Default);

        if (!confirmed)
        {
            return false;
        }

        await _deviceService.Delete(id);
        _deviceCache.Remove(id);
        return true;
    }

    public struct DeviceEntry
    {
        public Guid Id { get; private set; }
        public string Description { get; private set; }

        public static DeviceEntry New(Guid id, string desc)
        {
            return new DeviceEntry()
            {
                Id = id,
                Description = desc,
            };
        }

        public static DeviceEntry From(Device device)
        {
            return new DeviceEntry()
            {
                Id = device.Id,
                Description = device.Description,
            };
        }
    }
}